package com.eqxiu.ieditor.weixin.service;

import java.io.IOException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.redis.core.StringRedisTemplate;

import com.alibaba.dubbo.config.annotation.Service;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.eqxiu.ieditor.weixin.api.WeiXinServiceApi;
import com.eqxiu.ieditor.weixin.common.exception.NotFoundResourceException;
import com.eqxiu.ieditor.weixin.common.properties.WxProperties;
import com.eqxiu.ieditor.weixin.dao.WeixinAppDao;
import com.eqxiu.ieditor.weixin.dao.WeixinReplyDao;
import com.eqxiu.ieditor.weixin.dto.AccessToken;
import com.eqxiu.ieditor.weixin.dto.BaseEvent;
import com.eqxiu.ieditor.weixin.dto.JsTicket;
import com.eqxiu.ieditor.weixin.dto.QrTicket;
import com.eqxiu.ieditor.weixin.event.SubscribeEvent;
import com.eqxiu.ieditor.weixin.event.UnsubscribeEvent;
import com.eqxiu.ieditor.weixin.model.WeixinApp;
import com.eqxiu.ieditor.weixin.model.WeixinAppExample;
import com.eqxiu.ieditor.weixin.model.WeixinReply;
import com.eqxiu.ieditor.weixin.model.WeixinReplyExample;
import com.eqxiu.ieditor.weixin.util.HttpUtils;
import com.eqxiu.ieditor.weixin.util.NotFoundAppIdException;
import com.eqxiu.ieditor.weixin.util.WXPayUtil;
import com.eqxiu.user.api.UserApi;
import com.eqxiu.weixin.api.WeiXinMPApi;

/**
 * @description 微信Service
 * @author
 * @modified DuYoulong 重构
 * @creatTime 2019年4月9日
 * @since 1.0.0
 */
@Service
public class WeiXinServiceApiImpl implements WeiXinServiceApi {
    @Autowired
    private WxProperties properties;
    /**
     * TODO 技术栈=配置方式
     */

    @Autowired
    private WeixinAppDao appDao;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private WeixinReplyDao weixinReplyDao;
    @Autowired
    private UserApi userApi;

    @Autowired
    private WeiXinMPApi weixinMPApi;

    @Autowired
    private ApplicationEventPublisher eventPublisheer;

    private final Logger logger = LoggerFactory.getLogger(WeiXinServiceApiImpl.class);

    @Override
    public AccessToken getAccessToken(String appId) {
        WeixinAppExample example = new WeixinAppExample();
        example.createCriteria().andAppIdEqualTo(appId);
        List<WeixinApp> apps = appDao.selectByExample(example);
        logger.info("weixin: appId={}, dtos={}", appId, apps);

        if (apps == null || apps.size() == 0) {
            throw new NotFoundAppIdException("appid " + appId + " not found");
        }
        WeixinApp app = apps.get(0);
        String token = weixinMPApi.getWeChatAccessToken(app.getAppId());
        logger.info("weixin: token={}", token);
        // String url = String.format(properties.getTokenUrl(), app.getAppId(), app.getAppSecret());
        // String json = null;
        // try {
        // json = HttpUtils.get(url);
        // } catch (IOException e) {
        // url = String.format(properties.getTokenFallbackUrl(), app.getAppId(), app.getAppSecret());
        // logger.error("failed , try fallback url {}", url);
        // try {
        // json = HttpUtils.get(url);
        // } catch (IOException e1) {
        // logger.error("fallback url {} failed , fast-fail ", url);
        // throw new RuntimeException("get access token failed " + appId);
        // }
        // }
        //
        // if (Objects.isNull(json)) {
        // throw new RuntimeException("get access token failed " + appId);
        // }

        AccessToken accessToken = new AccessToken();
        accessToken.setAccessToken(token);
        // AccessToken token = JSON.parseObject(json, AccessToken.class);
        return accessToken;
    }

    @Override
    public AccessToken getAccessToken(String appId, String code) {

        WeixinAppExample example = new WeixinAppExample();
        example.createCriteria().andAppIdEqualTo(appId);
        List<WeixinApp> apps = appDao.selectByExample(example);
        logger.info("weixin: appId={}, code={}, dtos={}", appId, code, apps);
        if (apps == null || apps.size() == 0) {
            throw new NotFoundAppIdException("appid " + appId + " not found");
        }
        WeixinApp app = apps.get(0);
        String url = String.format(properties.getOauth2TokenUrl(), app.getAppId(), app.getAppSecret(), code);
        logger.info("weixin: url={}", url);
        String json = null;
        try {
            json = HttpUtils.get(url);
            logger.info("weixin: getJson={}", json);
        } catch (Exception e) {
            logger.error("weixin: 获取token失败，e={}", e);
        }

        if (Objects.isNull(json)) {
            throw new RuntimeException("get access token failed " + appId);
        }
        // {"errcode":40013,"errmsg":"invalid appid"}

        AccessToken token = JSON.parseObject(json, AccessToken.class);

        if (token.getErrcode() != 0) {
            logger.error("gen access token failed errcode {} , errormsg : {}", token.getErrcode(), token.getErrmsg());
            throw new NotFoundResourceException("gen access token failed " + token.getErrmsg());
        }

        return token;
    }

    @Override
    public QrTicket getLimitTicket(String appId, String sceneCode) {
        String cacheKey = "ie:wx:limitticket:" + appId + ":" + sceneCode;
        String json = redisTemplate.opsForValue().get(cacheKey);

        if (Objects.nonNull(json)) {
            return JSON.parseObject(json, QrTicket.class);
        }

        AccessToken token = getAccessToken(appId);

        String url = String.format(properties.getTicketUrl(), token.getAccessToken());
        String params =
            "{\"expire_seconds\": 604800, \"action_name\": \"QR_STR_SCENE\", \"action_info\": {\"scene\": {\"scene_str\": \""
                + sceneCode + "\"}}}";
        try {
            logger.debug("get limit ticket url {}", url);
            json = HttpUtils.post(url, params);
            logger.debug("get limit ticket result {}", json);
        } catch (IOException e) {
            url = String.format(properties.getTicketFallbackUrl(), token.getAccessToken());
            try {
                json = HttpUtils.post(url, params);
            } catch (IOException e1) {
                e1.printStackTrace();
                throw new RuntimeException("get limit ticket failed " + appId + " sceneCode: " + sceneCode);
            }
        }

        if (Objects.isNull(json)) {
            throw new RuntimeException("get limit ticket failed " + appId + " sceneCode: " + sceneCode);
        }

        QrTicket ticket = JSON.parseObject(json, QrTicket.class);
        redisTemplate.opsForValue().set(cacheKey, json, ticket.getExpireSeconds() - 200, TimeUnit.SECONDS);

        return ticket;
    }

    @Override
    public QrTicket getLimitTicket(String appId, long sceneId) {
        return null;
    }

    @Override
    public QrTicket getUnlimitTicket(String appId, String sceneCode) {
        String cacheKey = "ie:wx:limitticket:" + appId + ":" + sceneCode;
        String json = redisTemplate.opsForValue().get(cacheKey);

        if (Objects.nonNull(json)) {
            return JSON.parseObject(json, QrTicket.class);
        }

        // AccessToken token = getAccessToken(appId).get();

        WeixinAppExample example = new WeixinAppExample();
        example.createCriteria().andAppIdEqualTo(appId);
        List<WeixinApp> apps = appDao.selectByExample(example);
        if (apps == null || apps.size() == 0) {
            throw new NotFoundAppIdException("appid " + appId + " not found");
        }
        WeixinApp app = apps.get(0);

        String token = userApi.getWeChatAccessToken(appId, app.getAppSecret());
        if (StringUtils.isBlank(token)) {
            logger.error("token is null by appid {} and secret {}", appId, app.getAppSecret());
            throw new RuntimeException(
                "token is null while get limit ticket failed " + appId + " sceneCode: " + sceneCode);
        }

        String url = String.format(properties.getTicketUrl(), token);
        String params =
            "{\"expire_seconds\": 604800, \"action_name\": \"QR_STR_SCENE\", \"action_info\": {\"scene\": {\"scene_str\": \""
                + sceneCode + "\"}}}";
        try {
            logger.debug("get limit ticket url {}", url);
            json = HttpUtils.post(url, params);
            logger.debug("get limit ticket result {}", json);
        } catch (IOException e) {
            url = String.format(properties.getTicketFallbackUrl(), token);
            try {
                json = HttpUtils.post(url, params);
            } catch (IOException e1) {
                e1.printStackTrace();
                throw new RuntimeException("get limit ticket failed " + appId + " sceneCode: " + sceneCode);
            }
        }
        if (Objects.isNull(json)) {
            throw new RuntimeException("get limit ticket failed " + appId + " sceneCode: " + sceneCode);
        }

        QrTicket ticket = JSON.parseObject(json, QrTicket.class);

        if (StringUtils.isBlank(ticket.getTicket())) {
            logger.error("get unlimit ticket json failed {}", json);
            throw new RuntimeException("get unlimit ticket json failed " + json);
        }

        redisTemplate.opsForValue().set(cacheKey, json, ticket.getExpireSeconds() - 200, TimeUnit.SECONDS);

        return ticket;
    }

    @Override
    public QrTicket getUnlimitTicket(String appId, long sceneId) {
        return null;
    }

    @Override
    public String processNotify(String content) throws Exception {

        Map<String, String> root = WXPayUtil.xmlToMap(content);
        String fromUserName = root.get("FromUserName");
        // 获取接收方
        String toUserName = root.get("ToUserName");
        // 获取消息的类型
        String msgType = root.get("MsgType");

        logger.info("receive wx msg {}", root);

        if (msgType.equals(BaseEvent.MESSAGE_TYPE_EVENT)) {
            String eventType = root.get("Event").toLowerCase();
            // 关注事件,用户未关注时，进行关注后的事件推送
            String now = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
            if (eventType.equals(BaseEvent.EVENT_TYPE_SUBSCRIBE)) {
                String eventKey = root.get("EventKey");
                return processSubscribe(eventKey, fromUserName, toUserName, now);
            } else if (eventType.equals(BaseEvent.EVENT_TYPE_SCAN)) {
                // 扫描二维码事件,用户已关注时的事件推送
                String eventKey = root.get("EventKey");
                return handleScan(eventKey, fromUserName, toUserName, now);
            } else if (eventType.equals(BaseEvent.EVENT_TYPE_UNSUBSCRIBE)) {
                eventPublisheer.publishEvent(new UnsubscribeEvent(fromUserName, toUserName));
                return "";
            }
        } else if (msgType.equals(BaseEvent.MESSAGE_TYPE_TEXT)) {
            String userInputContent = root.get("Content");
            StringBuffer buffer =
                new StringBuffer("<xml><ToUserName><![CDATA[" + fromUserName + "]]></ToUserName><FromUserName><![CDATA["
                    + toUserName + "]]></FromUserName><CreateTime>" + System.currentTimeMillis() + "</CreateTime>");

            WeixinReplyExample example = new WeixinReplyExample();
            example.createCriteria().andMatchContentLike("%" + userInputContent + "%");
            List<WeixinReply> replies = weixinReplyDao.selectByExample(example);
            if (replies != null && !replies.isEmpty()) {
                WeixinReply reply = replies.get(0);

                switch (reply.getReplyType()) {
                    case "text":
                        buffer.append("<MsgType><![CDATA[text]]></MsgType><Content><![CDATA[" + reply.getReplyContent()
                            + "]]></Content></xml>");
                        break;

                    case "voice":
                        buffer.append("<MsgType><![CDATA[voice]]></MsgType><Voice><MediaId><![CDATA["
                            + reply.getReplyContent() + "]]></MediaId></Voice></xml>");
                        break;

                    case "img":

                        buffer.append("<MsgType><![CDATA[image]]></MsgType><Image><MediaId><![CDATA["
                            + reply.getReplyContent() + "]]></MediaId></Image></xml>");
                        break;

                    case "news":
                        buffer.append("<MsgType><![CDATA[news]]></MsgType><ArticleCount>1</ArticleCount><Content>"
                            + reply.getReplyContent() + "</Content>");
                        String replyInfo = reply.getReplyInfo();
                        if (replyInfo != null) {
                            JSONObject riObj = JSON.parseObject(replyInfo);
                            JSONObject first = riObj.getJSONArray("list").getJSONObject(0);
                            String title1 = first.getString("title");
                            String description1 = "";
                            String picurl = first.getString("cover_url");
                            String url = first.getString("content_url");
                            buffer.append("<Articles><item><Title><![CDATA[").append(title1)
                                .append("]]></Title><Description><![CDATA[").append(description1)
                                .append("]]></Description><PicUrl><![CDATA[").append(picurl)
                                .append("]]></PicUrl><Url><![CDATA[").append(url).append("]]></Url></item>")
                                .append("</Articles></xml>");
                            break;
                        } else {
                            return "";
                        }

                    default:
                        reply = weixinReplyDao.selectByPrimaryKey(4);
                        buffer.append("<MsgType><![CDATA[text]]></MsgType><Content><![CDATA[" + reply.getReplyContent()
                            + "]]></Content></xml>");
                        break;

                }
                return buffer.toString();
            } else {
                WeixinReply reply = weixinReplyDao.selectByPrimaryKey(4);
                return buffer.append("<MsgType><![CDATA[text]]></MsgType><Content><![CDATA[" + reply.getReplyContent()
                    + "]]></Content></xml>").toString();
            }
        }

        return "";
    }

    private String handleScan(String eventKey, String fromUserName, String toUserName, String now) {
        if (Objects.nonNull(eventKey)) {
            if (eventKey.startsWith("IE:QR:LOGIN_KEY")) {
                String old_wxloginqrvalue = redisTemplate.opsForValue().get(eventKey);
                if (old_wxloginqrvalue == null) {
                    old_wxloginqrvalue = userApi.getCacheForHandleEvent(eventKey);
                }
                String new_wxloginqrvalue = fromUserName + "|" + old_wxloginqrvalue;
                userApi.saveCacheForHandleEvent(eventKey, new_wxloginqrvalue);
                redisTemplate.opsForValue().set(eventKey, new_wxloginqrvalue);
                redisTemplate.opsForValue().increment("ie:scan:qr:" + now, 1);
            }
        }
        WeixinReply reply = weixinReplyDao.selectByPrimaryKey(3);

        return "<xml><ToUserName><![CDATA[" + fromUserName + "]]></ToUserName><FromUserName><![CDATA[" + toUserName
            + "]]></FromUserName><CreateTime>" + System.currentTimeMillis()
            + "</CreateTime><MsgType><![CDATA[text]]></MsgType><Content><![CDATA[" + reply.getReplyContent()
            + "]]></Content></xml>";
    }

    private String processSubscribe(String eventKey, String fromUserName, String toUserName, String now) {
        if (Objects.nonNull(eventKey)) {// 有参数
            String key = eventKey;
            if (key.startsWith("qrscene_")) {
                key = key.replace("qrscene_", "");
                if (key.startsWith("IE:QR:LOGIN_KEY")) {
                    String old_wxloginqrvalue = redisTemplate.opsForValue().get(key);// 做兼容代码
                    if (old_wxloginqrvalue == null) {
                        old_wxloginqrvalue = userApi.getCacheForHandleEvent(key);
                    }
                    String new_wxloginqrvalue = fromUserName + "|" + old_wxloginqrvalue;
                    redisTemplate.opsForValue().set(key, new_wxloginqrvalue);
                    userApi.saveCacheForHandleEvent(key, new_wxloginqrvalue);
                    redisTemplate.opsForValue().increment("ie:sub:qr:" + now, 1);
                }
            }
        }

        eventPublisheer.publishEvent(new SubscribeEvent(fromUserName, toUserName));

        WeixinReply reply = weixinReplyDao.selectByPrimaryKey(2);
        return "<xml><ToUserName><![CDATA[" + fromUserName + "]]></ToUserName><FromUserName><![CDATA[" + toUserName
            + "]]></FromUserName><CreateTime>12345678</CreateTime><MsgType><![CDATA[text]]></MsgType><Content><![CDATA["
            + reply.getReplyContent() + "]]></Content></xml>";
    }

    @Override
    public JsTicket getJsTicket(String appId, String type) {
        String cacheKey = "ie:wx:jsticket:" + appId + ":" + type;
        String json = redisTemplate.opsForValue().get(cacheKey);

        if (Objects.nonNull(json)) {
            return JSON.parseObject(json, JsTicket.class);
        }

        AccessToken token = getAccessToken(appId);
        if (token.getAccessToken() == null) {
            // try again, suppose success
            logger.warn("appid {} get access token failed, try again", appId);
            token = getAccessToken(appId);
        }

        String url = String.format(properties.getTicketJsUrl(), token.getAccessToken(), type);

        try {
            json = HttpUtils.get(url);
        } catch (IOException e) {
            e.printStackTrace();
        }

        JsTicket ticket = JSON.parseObject(json, JsTicket.class);
        if (ticket != null && ticket.getErrcode() == 0 && "ok".equals(ticket.getErrmsg())) {
            redisTemplate.opsForValue().set(cacheKey, json, 7000, TimeUnit.SECONDS);
            return ticket;
        }
        return null;
    }

    @Override
    public String getUserInfo(String accessToken, String openId) throws Exception {
        String encodeId = WXPayUtil.MD5(openId);
        String encacheKey = "ie_wx_user_" + encodeId;
        // 获取用户信息
        String userObj = redisTemplate.opsForValue().get(encacheKey);
        if (userObj == null) {
            try {
                userObj = HttpUtils.get(String.format(properties.getOauth2UserInfoUrl(), accessToken, openId));
            } catch (IOException e) {
                e.printStackTrace();
            }
            logger.info("wx user: {}", userObj);
            redisTemplate.opsForValue().set(encacheKey, userObj);
        }

        return userObj;
    }

    public String getOAuthOpenId(String appId, String code) throws Exception {
        String acessTokenCachedKey = "ie_wx_access_token_" + appId + "_" + code;
        String openIdCachedKey = "ie_wx_openid_" + appId + "_" + code;

        String accessToken = redisTemplate.opsForValue().get(acessTokenCachedKey);
        String openId = redisTemplate.opsForValue().get(openIdCachedKey);
        if (accessToken == null || openId == null) {
            AccessToken optional = getAccessToken(appId, code);
            accessToken = optional.getAccessToken();
            openId = optional.getOpenid();
            if (optional != null) {
                redisTemplate.opsForValue().set(acessTokenCachedKey, accessToken, 1, TimeUnit.HOURS);
                redisTemplate.opsForValue().set(openIdCachedKey, openId, 1, TimeUnit.HOURS);
            }
        }
        getUserInfo(accessToken, openId);
        logger.info("accessToken: {}, openId: {}", accessToken, openId);
        return openId;
    }

    @Override
    public Map<String, String> signJsApi(String appId, String url) {
        String timestamp = System.currentTimeMillis() + "";
        String nonceStr = Long.toHexString(Double.doubleToLongBits(Math.random()));

        Map<String, String> rMap = new HashMap<>();
        rMap.put("timestamp", timestamp);
        rMap.put("noncestr", nonceStr);
        rMap.put("url", url);
        rMap.put("jsapi_ticket", getJsTicket(appId, "jsapi").getTicket());
        rMap.put("sign", WXPayUtil.sha1(rMap));
        return rMap;
    }

    public static void main(String[] args) {
        String appId = "wx8a61ef75c858990e";
        String secret = "da4e4a4b248ef695a588afe4dff7bdd7";

        String url =
            String.format("https://sh.api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s",
                appId, secret);

        String json = null;
        try {
            json = HttpUtils.get(url);
        } catch (IOException e) {
        }
        System.out.println(json);

        AccessToken token = JSON.parseObject(json, AccessToken.class);

        String ticketUrl =
            String.format("https://sh.api.weixin.qq.com/cgi-bin/qrcode/create?access_token=%s", token.getAccessToken());
        String params =
            "{\"expire_seconds\": 604800, \"action_name\": \"QR_STR_SCENE\", \"action_info\": {\"scene\": {\"scene_str\": \""
                + "testscc" + "\"}}}";
        try {
            json = HttpUtils.post(ticketUrl, params);
        } catch (IOException e) {
        }
        System.out.println(json);

    }
}
